package com.games.ecoship.client;

import java.applet.Applet;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.GridLayout;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

import javax.swing.BoxLayout;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.border.LineBorder;

import com.games.ecoship.EcoShipConstants;

public class EcoShipClient extends Applet implements EcoShipConstants, Runnable {

	private static final long serialVersionUID = -8143049704023251053L;
	private static final int CLIENT_WIDTH  = 360;
	private static final int CLIENT_HEIGHT = 700;

	/** The game boards, initialized as a 10x10 array of Cells */
	private Cell[][] myBoard    = new Cell[NUM_ROWS][NUM_COLS];
	private Cell[][] enemyBoard = new Cell[NUM_ROWS][NUM_COLS];
	
	/** The player's number */
	private int playerNum = PLAYER1;
	
	/** Indicates whether the player has the turn or not */
	private Boolean isMyTurn = false;
	
	/** Game loop variable */
	private Boolean continueToPlay = true;
	
	/** Wait for the player to mark a cell */
	private Boolean waiting = true;
	
	/** Waiting for the player to finish setting up their board */
	private Boolean settingUp = true;
	
	/** The title label */
	private JLabel titleLabel = new JLabel();
	/** The status label */
	private JLabel statusLabel = new JLabel();
	
	/** The column that is selected */
	private int colSelected;
	/** The row that is selected */
	private int rowSelected;
	
	/** Input stream from the server */
	private DataInputStream fromServer;
	/** Output stream to to server */
	private DataOutputStream toServer;
	
	/** Indicates if this program runs as an application */
	private Boolean isStandAlone = true;
	
	/** Host name or IP */
	private String host = "localhost";

	/** Temporarily holds a cell that a player clicked, and this cell will be updated
	 *  to reflect if the cell attack was a hit or a miss 
	 */
	private Cell cellClicked;

	private int numShipsSet = 0;

	private Ship[] ships = new Ship[NUM_SHIPS];
	
	/** Initializes the GUI */
	public void init() {
		
		this.setSize(CLIENT_WIDTH, CLIENT_HEIGHT);
		
		// Initialize the reference to be used for click listening
		Cell.setReference(this);
		
		// Initialize the array containing the ships for this player (each ship is one 'shot' bigger than the previous)
		for (int count=0; count < NUM_SHIPS; count++)
			ships[count] = new Ship(Ship.getShipCount() + Ship.MIN_SHIP_SIZE);
		
		// Panel to hold the boards (The enemy board is on top, the player board is on the bottom)
		JPanel mainPanel = new JPanel();
		mainPanel.setLayout(new BoxLayout(mainPanel, BoxLayout.PAGE_AXIS));
		
		// Create and add the enemy board
		JLabel enemyLabel = new JLabel("Opponent's Board");
		mainPanel.add(enemyLabel);
		JPanel enemyPanel = new JPanel();
		enemyPanel.setLayout(new GridLayout(NUM_ROWS,NUM_COLS,0,0));
		enemyPanel.setPreferredSize(new Dimension(this.getWidth(), this.getHeight()));
		for (int rows=0; rows < NUM_ROWS; rows++) {
			for (int cols=0; cols < NUM_COLS; cols++) {
				enemyPanel.add(enemyBoard[rows][cols] = new Cell(rows, cols));
			}
		}
		enemyPanel.setAlignmentY(JPanel.TOP_ALIGNMENT);
		mainPanel.add(enemyPanel);
		
		// Space in between the boards, containing the status label
		statusLabel.setBorder(new LineBorder(Color.black, 2));
		statusLabel.setPreferredSize(new Dimension(this.getWidth(), 20));
		statusLabel.setHorizontalAlignment(JLabel.CENTER);
		statusLabel.setFont(new Font("Calibri", Font.BOLD, 16));
		statusLabel.setAlignmentY(JLabel.CENTER);
		statusLabel.setText("...");
		mainPanel.add(statusLabel);
		
		// Create and add the player board
		JPanel myPanel = new JPanel();
		JLabel myLabel = new JLabel("Your Board");
		mainPanel.add(myLabel);
		myPanel.setLayout(new GridLayout(NUM_ROWS,NUM_COLS,0,0));
		myPanel.setPreferredSize(new Dimension(this.getWidth(), this.getHeight()));
		for (int rows=0; rows < NUM_ROWS; rows++) {
			for (int cols=0; cols < NUM_COLS; cols++) {
				myPanel.add(myBoard[rows][cols] = new Cell(rows, cols));
			}
		}
		mainPanel.setAlignmentY(JPanel.BOTTOM_ALIGNMENT);
		mainPanel.add(myPanel);
		
		// Setup the properties for the labels and borders for labels and the panel
		mainPanel.setBorder(new LineBorder(Color.black, 1));
		mainPanel.setPreferredSize(new Dimension(this.getWidth(),this.getHeight()-20));
		titleLabel.setHorizontalAlignment(JLabel.CENTER);
		titleLabel.setFont(new Font("Calibri", Font.BOLD, 16));
		titleLabel.setBorder(new LineBorder(Color.black, 1));
		titleLabel.setText("Eco-Ship Game");
		titleLabel.setPreferredSize(new Dimension(this.getWidth(), 20));
		
		// Place the panel and the labels into the applet
		add(titleLabel, BorderLayout.PAGE_START);
		add(mainPanel, BorderLayout.CENTER);
		
		// Connect to the server
		connectToServer();
	}
	
	/** Connects to the server */
	private void connectToServer() {
		try {
			Socket socket;
			if (isStandAlone)
				socket = new Socket(host, PORT_NUM);
			else
				socket = new Socket(getCodeBase().getHost(), PORT_NUM);
			
			// Create an input stream to recieve data from the server
			fromServer = new DataInputStream(socket.getInputStream());
			
			// Create an output stream to send data to the server
			toServer = new DataOutputStream(socket.getOutputStream());
		}
		catch (Exception ex) {
			System.err.println(ex);
		}
		
		// Start a new thread to run a new game
		Thread game = new Thread(this);
		game.start();
	}
	
	@Override
	public void run() {
		
		try {
			
			// Server will notify the player which player they are in the game
			playerNum = fromServer.readInt();
			
			assignNumToCells();
			
			if (playerNum == PLAYER1) {
				
				// Change status
				//titleLabel.setText("Player 1 is 'X'");
				statusLabel.setText("Waiting for player 2 to join...");
				
				// Recieve startup notification from the server
				fromServer.readInt(); // Anything sent from the server will allow this task to continue
				
				// The other player has joined
				statusLabel.setText("Player 2 has joined. Click to place your 'ships' now.");
				
				// This player has the first turn
				isMyTurn = true;
			}
			else if (playerNum == PLAYER2) {    ////// (P1) You go first; click on the top board to fire at the enemy.
												////// (P2) Waiting for player 1 to set up pieces...
				// Change status
				//titleLabel.setText("Player 2 is 'O'");
				statusLabel.setText("Click to place your 'ships' now.");
			}
			
			// Allow players to set up their boards.
			setupBoard();
			
			// Send board info to the server
			sendBoardInfo();
			
			// Game loop
			while (continueToPlay) {
				if (playerNum == PLAYER1) {
					waitForPlayerAction();   // Wait for player 1 to make a move
					sendMove();              // Send the selected move to the server
					receiveInfoFromServer(); // Receive info from server about player 2's move
				}
				else if (playerNum == PLAYER2) {
					receiveInfoFromServer(); // Receive info from server about player 1's move
					waitForPlayerAction();   // Wait for player 2 to make a move
					sendMove();              // Send the selected move to the server
				}
			}
			
		} catch (Exception ex) {
			System.err.println(ex);
		}
	}

	private void assignNumToCells() {
		
		int otherPlayerNum;
		if (playerNum == PLAYER1)
			otherPlayerNum = PLAYER2;
		else
			otherPlayerNum = PLAYER1;
		
		// Set player num for the home board
		for (int rows=0; rows < NUM_ROWS; rows++) {
			for (int cols=0; cols < NUM_COLS; cols++) {
				myBoard[rows][cols].setPlayerNum(playerNum);
			}
		}
		
		// Set player num for the away board
		for (int rows=0; rows < NUM_ROWS; rows++) {
			for (int cols=0; cols < NUM_COLS; cols++) {
				enemyBoard[rows][cols].setPlayerNum(otherPlayerNum);
			}
		}
	}

	/**
	 * Waits for the player to set all of his / her ships before proceeding with the game.
	 * 
	 * @throws IOException
	 * @throws InterruptedException 
	 */
	private void setupBoard() throws IOException, InterruptedException {
		
		while (settingUp) {
			if (numShipsSet < NUM_SHIPS)
				Thread.sleep(100);
			else
				settingUp = false;
		}
	}
	
	/**
	 * Tells the server what is on this player's board.
	 * @throws IOException 
	 * 
	 */
	private void sendBoardInfo() throws IOException {
		
		// Informs the server that the player is ready to send the board info to it
		if (playerNum == PLAYER1)
			toServer.writeInt(PLAYER1);
		else
			toServer.writeInt(PLAYER2);
		
		// TODO (optimize): Technically, the only info needed to be sent 
		//                  are the locations of each ship object
		// Now, send all board data to the server
		for (int rows=0; rows < NUM_ROWS; rows++) {
			for (int cols=0; cols < NUM_COLS; cols++) {
				toServer.writeChar(myBoard[rows][cols].getToken());
			}
		}
		
	}

	/** The thread will continue to sleep until an action is picked up by a
	 *  clickListener assigned to the cells of the board. 'waiting' will be set
	 *  to false , to break out of the while loop, and then it will be reset for
	 *  the next time the game is waiting on the player. */
	private void waitForPlayerAction() throws InterruptedException {
		while (waiting) {
			Thread.sleep(100);
		}
		statusLabel.setText("Waiting for the other player to move.");
		waiting = true;
	}
	
	/** Send information about the move the player just made to the server. */
	private void sendMove() throws IOException {
		toServer.writeInt(colSelected);
		toServer.writeInt(rowSelected);
	}
	
	/** Receive information from the server. 
	 * @throws IOException */
	private void receiveInfoFromServer() throws IOException {
		
		// Recieve the game status, and determine if game is over or not
		int status = fromServer.readInt();
		
		// Player 1 wins
		if (status == PLAYER1_WON) {
			continueToPlay = false;
			
			if (playerNum == PLAYER1)
				statusLabel.setText("Player " + PLAYER1 + " wins! Good job!");
			else if (playerNum == PLAYER2) {
				statusLabel.setText("Player " + PLAYER2 + " loses. Better luck next game!");
				receiveMove();
			}
		}
		// Player 2 wins
		else if (status == PLAYER2_WON) {
			continueToPlay = false;
			
			if (playerNum == PLAYER2)
				statusLabel.setText("Player " + PLAYER2 + " wins! Good job!");
			else if (playerNum == PLAYER1) {
				statusLabel.setText("Player " + PLAYER1 + " loses. Better luck next game!");
				receiveMove();
			}
		}
		//Draw
		else if (status == DRAW) {
			continueToPlay = false;
			statusLabel.setText("The game is a draw, no winner!");
			
			if (playerNum == PLAYER2)
				receiveMove();
		}
		// Game continues
		else {
			receiveMove();
			statusLabel.setText("Your turn.");
			isMyTurn = true;
		}
	}

	/** Receive the other player's move from the server, and update the board. 
	 * @throws IOException */
	private void receiveMove() throws IOException {
		int col      = fromServer.readInt();
		int row      = fromServer.readInt();
		char hitType = fromServer.readChar();
		myBoard[col][row].setToken(hitType);
	}

	public Boolean isMyTurn() {
		return isMyTurn;
	}

	public void setTurn(boolean myTurn) {
		isMyTurn = myTurn;
	}

	public void setColSelected(int col) {
		colSelected = col;
	}
	
	public void setRowSelected(int row) {
		rowSelected = row;
	}

	public void setWaitingStatus(boolean isWaiting) {
		waiting = isWaiting;
	}

	public void setCellSelected(Cell cellClicked) {
		this.cellClicked = cellClicked;
	}

	public boolean settingUp() {
		return settingUp;
	}

	/**
	 * Increments the count of the number of ships set so far
	 */
	public void shipSet() {
		numShipsSet++;
		if (numShipsSet > NUM_SHIPS)
			System.err.print(new Exception("Too many ships have been set..."));
	}
	
	/**
	 * Starting at 0, this value returned represents how many ships the player has placed.
	 * 
	 * @return numShipsSet
	 */
	public int getNumShipsSet() {
		return numShipsSet;
	}

	public int getPlayerNum() {
		return playerNum;
	}

	/**
	 * Returns the ship at the specified index.
	 * 
	 * @param shipCount
	 * @return ships[shipCount]
	 */
	public Ship getShip(int shipCount) {
		if (shipCount < ships.length && shipCount >= 0)
			return ships [shipCount];
		else {
			System.err.println(new Exception("Invalid index."));
			return null;
		}
	}

	public Cell[][] getPlayerBoard() {
		return myBoard;
	}	
}
